/*
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.nio.file.attribute;

import java.util.*;

/**
 * An entry in an access control list (ACL).
 *
 * <p> The ACL entry represented by this class is based on the ACL model
 * specified in <a href="http://www.ietf.org/rfc/rfc3530.txt"><i>RFC&nbsp;3530:
 * Network File System (NFS) version 4 Protocol</i></a>. Each entry has four
 * components as follows:
 *
 * <ol>
 * <li><p> The {@link #type() type} component determines if the entry
 * grants or denies access. </p></li>
 *
 * <li><p> The {@link #principal() principal} component, sometimes called the
 * "who" component, is a {@link UserPrincipal} corresponding to the identity
 * that the entry grants or denies access
 * </p></li>
 *
 * <li><p> The {@link #permissions permissions} component is a set of
 * {@link AclEntryPermission permissions}
 * </p></li>
 *
 * <li><p> The {@link #flags flags} component is a set of {@link AclEntryFlag
 * flags} to indicate how entries are inherited and propagated </p></li>
 * </ol>
 *
 * <p> ACL entries are created using an associated {@link Builder} object by
 * invoking its {@link Builder#build build} method.
 *
 * <p> ACL entries are immutable and are safe for use by multiple concurrent
 * threads.
 *
 * @since 1.7
 */

public final class AclEntry {

  private final AclEntryType type;
  private final UserPrincipal who;
  private final Set<AclEntryPermission> perms;
  private final Set<AclEntryFlag> flags;

  // cached hash code
  private volatile int hash;

  // private constructor
  private AclEntry(AclEntryType type,
      UserPrincipal who,
      Set<AclEntryPermission> perms,
      Set<AclEntryFlag> flags) {
    this.type = type;
    this.who = who;
    this.perms = perms;
    this.flags = flags;
  }

  /**
   * A builder of {@link AclEntry} objects.
   *
   * <p> A {@code Builder} object is obtained by invoking one of the {@link
   * AclEntry#newBuilder newBuilder} methods defined by the {@code AclEntry}
   * class.
   *
   * <p> Builder objects are mutable and are not safe for use by multiple
   * concurrent threads without appropriate synchronization.
   *
   * @since 1.7
   */
  public static final class Builder {

    private AclEntryType type;
    private UserPrincipal who;
    private Set<AclEntryPermission> perms;
    private Set<AclEntryFlag> flags;

    private Builder(AclEntryType type,
        UserPrincipal who,
        Set<AclEntryPermission> perms,
        Set<AclEntryFlag> flags) {
      assert perms != null && flags != null;
      this.type = type;
      this.who = who;
      this.perms = perms;
      this.flags = flags;
    }

    /**
     * Constructs an {@link AclEntry} from the components of this builder.
     * The type and who components are required to have been set in order
     * to construct an {@code AclEntry}.
     *
     * @return a new ACL entry
     * @throws IllegalStateException if the type or who component have not been set
     */
    public AclEntry build() {
      if (type == null) {
        throw new IllegalStateException("Missing type component");
      }
      if (who == null) {
        throw new IllegalStateException("Missing who component");
      }
      return new AclEntry(type, who, perms, flags);
    }

    /**
     * Sets the type component of this builder.
     *
     * @param type the component type
     * @return this builder
     */
    public Builder setType(AclEntryType type) {
      if (type == null) {
        throw new NullPointerException();
      }
      this.type = type;
      return this;
    }

    /**
     * Sets the principal component of this builder.
     *
     * @param who the principal component
     * @return this builder
     */
    public Builder setPrincipal(UserPrincipal who) {
      if (who == null) {
        throw new NullPointerException();
      }
      this.who = who;
      return this;
    }

    // check set only contains elements of the given type
    private static void checkSet(Set<?> set, Class<?> type) {
      for (Object e : set) {
        if (e == null) {
          throw new NullPointerException();
        }
        type.cast(e);
      }
    }

    /**
     * Sets the permissions component of this builder. On return, the
     * permissions component of this builder is a copy of the given set.
     *
     * @param perms the permissions component
     * @return this builder
     * @throws ClassCastException if the set contains elements that are not of type {@code
     * AclEntryPermission}
     */
    public Builder setPermissions(Set<AclEntryPermission> perms) {
      if (perms.isEmpty()) {
        // EnumSet.copyOf does not allow empty set
        perms = Collections.emptySet();
      } else {
        // copy and check for erroneous elements
        perms = EnumSet.copyOf(perms);
        checkSet(perms, AclEntryPermission.class);
      }

      this.perms = perms;
      return this;
    }

    /**
     * Sets the permissions component of this builder. On return, the
     * permissions component of this builder is a copy of the permissions in
     * the given array.
     *
     * @param perms the permissions component
     * @return this builder
     */
    public Builder setPermissions(AclEntryPermission... perms) {
      Set<AclEntryPermission> set = EnumSet.noneOf(AclEntryPermission.class);
      // copy and check for null elements
      for (AclEntryPermission p : perms) {
        if (p == null) {
          throw new NullPointerException();
        }
        set.add(p);
      }
      this.perms = set;
      return this;
    }

    /**
     * Sets the flags component of this builder. On return, the flags
     * component of this builder is a copy of the given set.
     *
     * @param flags the flags component
     * @return this builder
     * @throws ClassCastException if the set contains elements that are not of type {@code
     * AclEntryFlag}
     */
    public Builder setFlags(Set<AclEntryFlag> flags) {
      if (flags.isEmpty()) {
        // EnumSet.copyOf does not allow empty set
        flags = Collections.emptySet();
      } else {
        // copy and check for erroneous elements
        flags = EnumSet.copyOf(flags);
        checkSet(flags, AclEntryFlag.class);
      }

      this.flags = flags;
      return this;
    }

    /**
     * Sets the flags component of this builder. On return, the flags
     * component of this builder is a copy of the flags in the given
     * array.
     *
     * @param flags the flags component
     * @return this builder
     */
    public Builder setFlags(AclEntryFlag... flags) {
      Set<AclEntryFlag> set = EnumSet.noneOf(AclEntryFlag.class);
      // copy and check for null elements
      for (AclEntryFlag f : flags) {
        if (f == null) {
          throw new NullPointerException();
        }
        set.add(f);
      }
      this.flags = set;
      return this;
    }
  }

  /**
   * Constructs a new builder. The initial value of the type and who
   * components is {@code null}. The initial value of the permissions and
   * flags components is the empty set.
   *
   * @return a new builder
   */
  public static Builder newBuilder() {
    Set<AclEntryPermission> perms = Collections.emptySet();
    Set<AclEntryFlag> flags = Collections.emptySet();
    return new Builder(null, null, perms, flags);
  }

  /**
   * Constructs a new builder with the components of an existing ACL entry.
   *
   * @param entry an ACL entry
   * @return a new builder
   */
  public static Builder newBuilder(AclEntry entry) {
    return new Builder(entry.type, entry.who, entry.perms, entry.flags);
  }

  /**
   * Returns the ACL entry type.
   *
   * @return the ACL entry type
   */
  public AclEntryType type() {
    return type;
  }

  /**
   * Returns the principal component.
   *
   * @return the principal component
   */
  public UserPrincipal principal() {
    return who;
  }

  /**
   * Returns a copy of the permissions component.
   *
   * <p> The returned set is a modifiable copy of the permissions.
   *
   * @return the permissions component
   */
  public Set<AclEntryPermission> permissions() {
    return new HashSet<AclEntryPermission>(perms);
  }

  /**
   * Returns a copy of the flags component.
   *
   * <p> The returned set is a modifiable copy of the flags.
   *
   * @return the flags component
   */
  public Set<AclEntryFlag> flags() {
    return new HashSet<AclEntryFlag>(flags);
  }

  /**
   * Compares the specified object with this ACL entry for equality.
   *
   * <p> If the given object is not an {@code AclEntry} then this method
   * immediately returns {@code false}.
   *
   * <p> For two ACL entries to be considered equals requires that they are
   * both the same type, their who components are equal, their permissions
   * components are equal, and their flags components are equal.
   *
   * <p> This method satisfies the general contract of the {@link
   * java.lang.Object#equals(Object) Object.equals} method. </p>
   *
   * @param ob the object to which this object is to be compared
   * @return {@code true} if, and only if, the given object is an AclEntry that is identical to this
   * AclEntry
   */
  @Override
  public boolean equals(Object ob) {
    if (ob == this) {
      return true;
    }
    if (ob == null || !(ob instanceof AclEntry)) {
      return false;
    }
    AclEntry other = (AclEntry) ob;
    if (this.type != other.type) {
      return false;
    }
    if (!this.who.equals(other.who)) {
      return false;
    }
    if (!this.perms.equals(other.perms)) {
      return false;
    }
    if (!this.flags.equals(other.flags)) {
      return false;
    }
    return true;
  }

  private static int hash(int h, Object o) {
    return h * 127 + o.hashCode();
  }

  /**
   * Returns the hash-code value for this ACL entry.
   *
   * <p> This method satisfies the general contract of the {@link
   * Object#hashCode} method.
   */
  @Override
  public int hashCode() {
    // return cached hash if available
    if (hash != 0) {
      return hash;
    }
    int h = type.hashCode();
    h = hash(h, who);
    h = hash(h, perms);
    h = hash(h, flags);
    hash = h;
    return hash;
  }

  /**
   * Returns the string representation of this ACL entry.
   *
   * @return the string representation of this entry
   */
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();

    // who
    sb.append(who.getName());
    sb.append(':');

    // permissions
    for (AclEntryPermission perm : perms) {
      sb.append(perm.name());
      sb.append('/');
    }
    sb.setLength(sb.length() - 1); // drop final slash
    sb.append(':');

    // flags
    if (!flags.isEmpty()) {
      for (AclEntryFlag flag : flags) {
        sb.append(flag.name());
        sb.append('/');
      }
      sb.setLength(sb.length() - 1);  // drop final slash
      sb.append(':');
    }

    // type
    sb.append(type.name());
    return sb.toString();
  }
}
